﻿using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Common;
using System.Data.Entity;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Xejen.Database.Entities;

namespace Xejen.Database.Ef
{
    /// <summary>
    /// 通过Ef技术进行仓储访问
    /// </summary>
    /// <typeparam name="TDbContext">表示数据库上下文对象<see cref="DbContext"/></typeparam>
    /// <typeparam name="TEntity">表示要访问哪张表的实体映射</typeparam>
    /// <typeparam name="TPrimaryKey">表示要访问的表的主键Id的数据类型</typeparam>
    public class EfRepository<TDbContext, TEntity, TPrimaryKey> : RepositoryBase<TEntity, TPrimaryKey>, IRepositoryWithDbContext
        where TEntity : class, IEntity<TPrimaryKey>
        where TPrimaryKey : struct
        where TDbContext : DbContext
    {
        private bool _disposed;

        /// <inheritdoc cref="IDbContextProvider{TDbContext}.DbContext"/>
        public virtual TDbContext Context => _dbContextProvider.GetDbContext();

        /// <summary>
        /// 表示指定的实体表对象
        /// </summary>
        public virtual DbSet<TEntity> Table => Context.Set<TEntity>();

        /// <inheritdoc cref="DbConnection" path="/summary"/>
        public virtual DbConnection Connection
        {
            get
            {
                var connection = Context.Database.Connection;

                if (connection.State != ConnectionState.Open)
                {
                    connection.Open();
                }

                return connection;
            }
        }

        private readonly IDbContextProvider<TDbContext> _dbContextProvider;

        /// <summary>
        /// <inheritdoc cref="EfRepository{TDbContext, TEntity, TPrimaryKey}" path="/summary"/>
        /// </summary>
        /// <param name="dbContextProvider"><inheritdoc cref="IDbContextProvider{TDbContext}" path="/summary"/></param>
        public EfRepository(IDbContextProvider<TDbContext> dbContextProvider)
        {
            _dbContextProvider = dbContextProvider;
        }

        /// <inheritdoc/>
        public override IQueryable<TEntity> GetAll()
        {
            return Table;
        }

        /// <inheritdoc/>
        public override Task<IQueryable<TEntity>> GetAllAsync()
        {
            return Task.FromResult(Table.AsQueryable());
        }

        /// <inheritdoc/>
        public override IQueryable<TEntity> GetAllIncluding(params Expression<Func<TEntity, object>>[] propertySelectors)
        {
            if (propertySelectors.IsNullOrEmpty())
            {
                return GetAll();
            }

            var query = GetAll();

            foreach (var propertySelector in propertySelectors)
            {
                query = query.Include(propertySelector);
            }

            return query;
        }

        /// <inheritdoc/>
        public override async Task<List<TEntity>> GetAllListAsync()
        {
            var query = await GetAllAsync();
            return await query.ToListAsync(CancellationToken.None);
        }

        /// <inheritdoc/>
        public override async Task<List<TEntity>> GetAllListAsync(Expression<Func<TEntity, bool>> predicate)
        {
            var query = await GetAllAsync();
            return await query.Where(predicate).ToListAsync(CancellationToken.None);
        }

        /// <inheritdoc/>
        public override async Task<TEntity> SingleAsync(Expression<Func<TEntity, bool>> predicate)
        {
            var query = await GetAllAsync();
            return await query.SingleAsync(predicate, CancellationToken.None);
        }

        /// <inheritdoc/>
        public override async Task<TEntity> FirstOrDefaultAsync(TPrimaryKey id)
        {
            var query = await GetAllAsync();
            return await query.FirstOrDefaultAsync(CreateEqualityExpressionForId(id), CancellationToken.None);
        }

        /// <inheritdoc/>
        public override async Task<TEntity> FirstOrDefaultAsync(Expression<Func<TEntity, bool>> predicate)
        {
            var query = await GetAllAsync();
            return await query.FirstOrDefaultAsync(predicate, CancellationToken.None);
        }

        /// <inheritdoc/>
        public override TEntity Insert(TEntity entity)
        {
            return Table.Add(entity);
        }

        /// <inheritdoc/>
        public override Task<TEntity> InsertAsync(TEntity entity)
        {
            return Task.FromResult(Table.Add(entity));
        }

        /// <inheritdoc/>
        public override TPrimaryKey InsertAndGetId(TEntity entity)
        {
            entity = Insert(entity);

            if (entity.IsTransient())
            {
                Context.SaveChanges();
            }

            return entity.Id;
        }

        /// <inheritdoc/>
        public override async Task<TPrimaryKey> InsertAndGetIdAsync(TEntity entity)
        {
            entity = await InsertAsync(entity);

            if (entity.IsTransient())
            {
                await Context.SaveChangesAsync(CancellationToken.None);
            }

            return entity.Id;
        }

        /// <inheritdoc/>
        public override TPrimaryKey InsertOrUpdateAndGetId(TEntity entity)
        {
            entity = InsertOrUpdate(entity);

            if (entity.IsTransient())
            {
                Context.SaveChanges();
            }

            return entity.Id;
        }

        /// <inheritdoc/>
        public override async Task<TPrimaryKey> InsertOrUpdateAndGetIdAsync(TEntity entity)
        {
            entity = await InsertOrUpdateAsync(entity);

            if (entity.IsTransient())
            {
                await Context.SaveChangesAsync(CancellationToken.None);
            }

            return entity.Id;
        }

        /// <inheritdoc/>
        public override TEntity Update(TEntity entity)
        {
            AttachIfNot(entity);
            Context.Entry(entity).State = EntityState.Modified;
            return entity;
        }

        /// <inheritdoc/>
        public override Task<TEntity> UpdateAsync(TEntity entity)
        {
            AttachIfNot(entity);
            Context.Entry(entity).State = EntityState.Modified;
            return Task.FromResult(entity);
        }

        /// <inheritdoc/>
        public override void Delete(TEntity entity)
        {
            AttachIfNot(entity);
            Table.Remove(entity);
        }

        /// <inheritdoc/>
        public override void Delete(TPrimaryKey id)
        {
            var entity = Table.Local.FirstOrDefault(ent => EqualityComparer<TPrimaryKey>.Default.Equals(ent.Id, id));
            if (entity == null)
            {
                entity = FirstOrDefault(id);
                if (entity == null)
                {
                    return;
                }
            }

            Delete(entity);
        }

        /// <inheritdoc/>
        public override void Delete(Expression<Func<TEntity, bool>> predicate)
        {
            var entities = Table.Where(predicate);
            Table.RemoveRange(entities);
        }

        /// <inheritdoc/>
        public override async Task DeleteAsync(Expression<Func<TEntity, bool>> predicate)
        {
            Delete(predicate);

            await Task.CompletedTask;
        }

        /// <inheritdoc/>
        public override async Task<int> CountAsync()
        {
            var query = await GetAllAsync();
            return await query.CountAsync(CancellationToken.None);
        }

        /// <inheritdoc/>
        public override async Task<int> CountAsync(Expression<Func<TEntity, bool>> predicate)
        {
            var query = await GetAllAsync();
            return await query.Where(predicate).CountAsync(CancellationToken.None);
        }

        /// <inheritdoc/>
        public override async Task<long> LongCountAsync()
        {
            var query = await GetAllAsync();
            return await query.LongCountAsync(CancellationToken.None);
        }

        /// <inheritdoc/>
        public override async Task<long> LongCountAsync(Expression<Func<TEntity, bool>> predicate)
        {
            var query = await GetAllAsync();
            return await query.Where(predicate).LongCountAsync(CancellationToken.None);
        }

        /// <inheritdoc/>
        protected virtual void AttachIfNot(TEntity entity)
        {
            if (!Table.Local.Contains(entity))
            {
                Table.Attach(entity);
            }
        }

        /// <inheritdoc/>
        public DbContext GetDbContext()
        {
            return Context;
        }

        /// <inheritdoc/>
        public override int SaveChanges()
        {
            return Context.SaveChanges();
        }

        /// <inheritdoc/>
        public override async Task<int> SaveChangesAsync()
        {
            return await Context.SaveChangesAsync();
        }

        /// <inheritdoc/>
        protected override void Dispose(bool disposing)
        {
            if (_disposed)
            {
                return;
            }

            if (disposing)
            {
                Context.Dispose();

                _disposed = true;
            }

            base.Dispose(disposing);
        }
    }
}
